package com.author.surface;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;

import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JTextPane;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.html.BlockView;
import javax.swing.text.html.CSS;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLDocument.Iterator;

import com.author.document.editorkit.ExtendedEditorKit;
import com.author.document.model.ExtendedDocument;
import com.author.document.style.DefaultStyleConstant;
import com.author.document.style.ExtendedDefaultStyleContext;
import com.author.document.style.StyleChangeNotify;
import com.author.document.style.StyleObserver;
import com.author.document.view.MultiPageSectionView;
import com.author.document.view.PageConstants;

public final class TextPane extends JTextPane implements CaretListener,
		PropertyChangeListener, Printable {
	/** Width and height of the Text Pane */
	public final static int				TEXTPANE_WIDTH			= 739;
	public final static int				TEXTPANE_HEIGHT			= 840;

	/** X and Y loc. of the Text Pane relative to the Canvas Pane coordinates */
	public final static int				TEXTPANE_XLOCATION		= 20;
	public final static int				TEXTPANE_YLOCATION		= 20;

	/** Border color and width of the Text Pane */
	public final static Color			TEXTPANE_BORDERCOLOR	= Color.lightGray;
	public final static int				TEXTPANE_BORDERWIDTH	= 1;

	/* Font information of the Text Pane */
	private Font						defaultTextFont;

	/* Text Pane will respond to style change notification */
	private StyleChangeNotify			propertyChangeNotify;

	/*
	 * The main style of the document - this is the default paragraph style.
	 * This style should not be changed once it is initialized
	 */
	private Style						mainStyle;

	/** The maximum left indentation allowed (currently set at 120) */
	public static final int				MAXIMUM_LEFT_INDENT		= 120;

	/*
	 * The style observer will be responsible for collecting character and
	 * paragraph attributes at different caret positions. It will then send a
	 * message to UIupdater so that different UI buttons can respond to styles
	 * at different caret positions
	 */
	private StyleObserver				currentStyleObserver	= new StyleObserver();

	/* Dot and mark represents the current positions of the caret */
	protected static int				dot						= 0;
	protected static int				mark					= 0;

	/* StyleContext associated with the Text Pane */
	private ExtendedDefaultStyleContext	defaultStyleContext;

	/* Document model for the Text Pane */
	private static HTMLDocument			document;

	/* EditorKit of the Text Pane */
	private static HTMLEditorKit		editorKit;

	/* Content type of the Editor Kit */
	private static final String			HTML_CONTENT_TYPE		= "text/html";

	/* First line of text to be inserted to the text pane */
	private static final String			INITIAL_HTML			= "<HTML><HEAD></HEAD><BODY></BODY></HTML>";

	/* Header and footer of the Text Pane */
	private JEditorPane					header					= null;
	private JEditorPane					footer					= null;

	/*
	 * Header and footer valid flag. Set to false whenever the text pane wants
	 * section view to re-calculate the sizes of the header and footer
	 */
	private static boolean				isHFsizevalid			= true;

	protected TextPane() {
		/* Set editor kit and document model for the Text Pane */
		setEditorKitForContentType(HTML_CONTENT_TYPE, new ExtendedEditorKit());
		setContentType(HTML_CONTENT_TYPE);
		setText(INITIAL_HTML);
		editorKit = (HTMLEditorKit) this.getEditorKit();
		document = (HTMLDocument) this.getStyledDocument();

		/* Set up the visible properties of the Text Pane */
		setSize(new Dimension(TEXTPANE_WIDTH, TEXTPANE_HEIGHT));
		setBounds(new Rectangle(TEXTPANE_XLOCATION, TEXTPANE_YLOCATION,
				getWidth(), getHeight()));
		setLocation(TEXTPANE_XLOCATION, TEXTPANE_YLOCATION);
		setBorder(new EmptyBorder(new Insets(PageConstants.PAGE_TOP_INSET,
				PageConstants.PAGE_LEFT_INSET, PageConstants.PAGE_BOTTOM_INSET,
				PageConstants.PAGE_RIGHT_INSET)));

		// Set up default font for the document
		defaultTextFont = new Font(
				DefaultStyleConstant.DEFAULT_STYLE_FONT_NAME, Font.PLAIN,
				DefaultStyleConstant.NORMAL_STYLE_FONT_SIZE);
		setFont(defaultTextFont);

		// Set up styles for the document
		defaultStyleContext = new ExtendedDefaultStyleContext();
		mainStyle = defaultStyleContext.getDefaultStyle();
		setLogicalStyle(mainStyle);

		/* Set up the auxiliary visible properties of the document */
		init();
		grabFocus();
		setCaretPosition(0);
		setVisible(true);
	}

	private void init() {
		/* Allow text pane to receive document style change notifications */
		propertyChangeNotify = StyleChangeNotify.getStyleChangeNotify();
		propertyChangeNotify.addPropertyChangeListener(this);

		/* Allow text pane to receive caret event change notifications */
		addCaretListener(this);
	}

	@Override
	public void propertyChange(PropertyChangeEvent event) {
		if (dot == mark) {
			// change document style property
			if (event.getPropertyName()
					.equals(StyleChangeNotify.DOCUMENT_STYLE)) {
				Style style = defaultStyleContext.getStyle((String) event
						.getNewValue());
				// update document with the new document style
				if (style != null) {
					setCharacterAttributes(style, true);
					setCaretPosition(dot);
				}
			}

			// change document font name
			if (event.getPropertyName().equals(StyleChangeNotify.FONT_NAME)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
					setLogicalStyle(mainStyle);
				}

				style.removeAttribute(StyleConstants.FontFamily);
				style.addAttribute(StyleConstants.FontFamily,
						event.getNewValue());

				// change character and paragraph attributes with the newly
				// updated style which has the new font added to it
				setCharacterAttributes(style, false);
				setCaretPosition(dot);
			}

			// change document font size
			if (event.getPropertyName().equals(StyleChangeNotify.FONT_SIZE)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
					setLogicalStyle(mainStyle);
				}

				// change font size of the document
				style.removeAttribute(StyleConstants.FontSize);
				style.addAttribute(StyleConstants.FontSize, event.getNewValue());

				// change character and paragraph attributes with the newly
				// updated style which has the new font size added to it
				setCharacterAttributes(style, false);
				setCaretPosition(dot);
			}

			// Make text Superscript
			if (event.getPropertyName().equals(StyleChangeNotify.SUPERSCRIPT)) {
				Style style;
				Style currentStyle = document.getLogicalStyle(dot);
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setSuperscript(style, true);
				} else if ((oldValue == true) && (newValue == false)) {
					style.addAttribute(StyleConstants.Superscript, false);
				}

				setCharacterAttributes(style, false);
				setCaretPosition(dot);
			}

			// Make text Subscript
			if (event.getPropertyName().equals(StyleChangeNotify.SUBSCRIPT)) {
				Style style;
				Style currentStyle = document.getLogicalStyle(dot);
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setSubscript(style, true);
				} else if ((oldValue == true) && (newValue == false)) {
					style.addAttribute(StyleConstants.Subscript, false);
				}

				setCharacterAttributes(style, false);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the alignment of the current paragraph to
			 * left alignment
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.LEFT_ALIGN)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setAlignment(style,
							StyleConstants.ALIGN_LEFT);
				} else if ((oldValue == true) && (newValue == false)) {
					/*
					 * Left alignment is the default alignment attribute, so we
					 * don't explicitly remove it
					 */
				}

				// alignment is a paragraph level attribute, so the alignment of
				// the entire paragraph is changed to reflect the new alignment
				setParagraphAttributes(style, false);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the alignment of the current paragraph to
			 * center alignment
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.CENTER_ALIGN)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setAlignment(style,
							StyleConstants.ALIGN_CENTER);
				} else if ((oldValue == true) && (newValue == false)) {
					style.removeAttribute(StyleConstants.ALIGN_CENTER);
				}

				// alignment is a paragraph level attribute, so the alignment of
				// the entire paragraph is changed to reflect the new alignment
				setParagraphAttributes(style, false);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the alignment of the current paragraph to
			 * right alignment
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.RIGHT_ALIGN)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setAlignment(style,
							StyleConstants.ALIGN_RIGHT);
				} else if ((oldValue == true) && (newValue == false)) {
					style.removeAttribute(StyleConstants.ALIGN_RIGHT);
				}

				// alignment is a paragraph level attribute, so the alignment of
				// the entire paragraph is changed to reflect the new alignment
				setParagraphAttributes(style, false);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the alignment of the current paragraph to
			 * justify alignment
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.JUSTIFY_ALIGN)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null)
					style = defaultStyleContext.addStyle(null, currentStyle);
				else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setAlignment(style,
							StyleConstants.ALIGN_JUSTIFIED);
				} else if ((oldValue == true) && (newValue == false)) {
					style.removeAttribute(StyleConstants.ALIGN_JUSTIFIED);
				}

				// alignment is a paragraph level attribute, so the alignment of
				// the entire paragraph is changed to reflect the new alignment
				setParagraphAttributes(style, false);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the line spacing of the current paragraph
			 * to single space
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.SINGLESPACE)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setLineSpacing(style,
							LineSpacingButtonGroup.SingleSpace.SINGLE_SPACING);
				} else if ((oldValue == true) && (newValue == false)) {
					/*
					 * The default line spacing is one line. So we don't
					 * explicitly remove one line spacing
					 */
				}

				setParagraphAttributes(style, true);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the line spacing of the current paragraph
			 * to one half space
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.ONEHALFSPACE)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants
							.setLineSpacing(
									style,
									LineSpacingButtonGroup.OneHalfSpace.ONEHALF_SPACING);
				} else if ((oldValue == true) && (newValue == false)) {
					style.removeAttribute(StyleConstants.LineSpacing);
					StyleConstants.setLineSpacing(style,
							LineSpacingButtonGroup.SingleSpace.SINGLE_SPACING);
				}

				setParagraphAttributes(style, true);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to set the line spacing of the current paragraph
			 * to double space
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.DOUBLESPACE)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				boolean oldValue = (Boolean) event.getOldValue();
				boolean newValue = (Boolean) event.getNewValue();

				if ((oldValue == false) && (newValue == true)) {
					StyleConstants.setLineSpacing(style,
							LineSpacingButtonGroup.DoubleSpace.DOUBLE_SPACING);
				} else if ((oldValue == true) && (newValue == false)) {
					style.removeAttribute(StyleConstants.LineSpacing);
					StyleConstants.setLineSpacing(style,
							LineSpacingButtonGroup.DoubleSpace.DOUBLE_SPACING);
				}

				setParagraphAttributes(style, true);
				setCaretPosition(dot);
			}

			/*
			 * The user wants to increase the left indentation of the current
			 * paragraph (the paragraph that contains the caret)
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.LEFT_INDENT)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null)
					style = defaultStyleContext.addStyle(null, currentStyle);
				else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				Float currentValue = (Float) this.getParagraphAttributes()
						.getAttribute(StyleConstants.LeftIndent);
				Float newValue = new Float(Float.parseFloat((String) event
						.getNewValue()));

				if (currentValue == null) {
					style.addAttribute(StyleConstants.LeftIndent,
							(Object) newValue);
					setParagraphAttributes(style, false);
				} else if (currentValue < newValue) {

					/*
					 * The user specified left margin value is greater than the
					 * current left margin value of the paragraph
					 */
					if (currentValue < TextPane.MAXIMUM_LEFT_INDENT) {
						style.removeAttribute(StyleConstants.LeftIndent);
						style.addAttribute(StyleConstants.LeftIndent,
								(Object) newValue);
						setParagraphAttributes(style, false);
					}
				} else if (currentValue > newValue) {

					/*
					 * This can happen when the user clicks the right indent
					 * button with the intention of decreasing the right
					 * indentation of the current paragraph
					 */

					style.addAttribute(StyleConstants.LeftIndent,
							(Object) newValue);
					setParagraphAttributes(style, false);
				}

				setCaretPosition(dot);
			}

			/* Change the foreground color */
			if (event.getPropertyName().equals(
					StyleChangeNotify.FOREGROUND_COLOR)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				Color oldValue = (Color) getParagraphAttributes().getAttribute(
						StyleConstants.Foreground);
				Color newValue = (Color) event.getNewValue();

				if (newValue == null) {
					return;
				}

				if (oldValue != newValue) {
					StyleConstants.setForeground(style, newValue);
					style.addAttribute(StyleConstants.Foreground,
							(Object) newValue);
					setCharacterAttributes(style, false);
				}

				setCaretPosition(dot);
			}

			/* Change the background color */
			if (event.getPropertyName().equals(
					StyleChangeNotify.BACKGROUND_COLOR)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				Color oldValue = (Color) getParagraphAttributes().getAttribute(
						StyleConstants.Background);
				Color newValue = (Color) event.getNewValue();

				if (newValue == null) {
					return;
				}

				if (oldValue != newValue) {
					StyleConstants.setBackground(style, newValue);
					style.addAttribute(StyleConstants.Background,
							(Object) newValue);
					setCharacterAttributes(style, false);
				}

				setCaretPosition(dot);
			}

		} else if (dot != mark) {
			/*
			 * If there is a current caret selection, and the user clicks to
			 * change the document style then the new document style is updated
			 * to the entire paragraph
			 */
			if (event.getPropertyName()
					.equals(StyleChangeNotify.DOCUMENT_STYLE)) {
				Style style = defaultStyleContext.getStyle((String) event
						.getNewValue());
				// update the current paragraph with the new document style
				if (style != null) {
					setParagraphAttributes(style, true);
				}
			}

			/*
			 * if user selects a different font name and there is a selection,
			 * then the font change is applied to the entire selection
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.FONT_NAME)) {
				Style style;
				Style currentStyle = getLogicalStyle();

				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
					setLogicalStyle(mainStyle);
				}

				// update the new font to the selection range of the text
				StyleConstants.setFontFamily(style,
						(String) event.getNewValue());
				if (dot >= mark) {
					document.setCharacterAttributes(mark, (dot - mark), style,
							false);
				} else {
					document.setCharacterAttributes(dot, (mark - dot), style,
							false);
				}
			}

			/*
			 * If user selects a different font size and there is a selection,
			 * then the font size change is applied to the entire selection
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.FONT_SIZE)) {
				Style style;
				Style currentStyle = getLogicalStyle();

				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
					setLogicalStyle(mainStyle);
				}

				// update the new font size to the selection range of the text
				StyleConstants
						.setFontSize(style, (Integer) event.getNewValue());
				if (dot >= mark) {
					document.setCharacterAttributes(mark, (dot - mark), style,
							false);
				} else {
					document.setCharacterAttributes(dot, (mark - dot), style,
							false);
				}
			}

			// Make document font SUPERSCRIPT for the selected text range
			if (event.getPropertyName().equals(StyleChangeNotify.SUPERSCRIPT)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}

				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setSuperscript(style, true);
				if (dot >= mark) {
					document.setCharacterAttributes(mark, (dot - mark), style,
							false);
				} else {
					document.setCharacterAttributes(dot, (mark - dot), style,
							false);
				}
			}

			// Make document font SUBSCRIPT for the selected text range
			if (event.getPropertyName().equals(StyleChangeNotify.SUBSCRIPT)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}

				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setSubscript(style, true);
				if (dot >= mark) {
					document.setCharacterAttributes(mark, (dot - mark), style,
							false);
				} else {
					document.setCharacterAttributes(dot, (mark - dot), style,
							false);
				}
			}

			// Make the entire paragraph left aligned
			if (event.getPropertyName().equals(StyleChangeNotify.LEFT_ALIGN)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setAlignment(style, StyleConstants.ALIGN_LEFT);
				setParagraphAttributes(style, false);
			}

			// Make the entire paragraph center aligned
			if (event.getPropertyName().equals(StyleChangeNotify.CENTER_ALIGN)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setAlignment(style, StyleConstants.ALIGN_CENTER);
				setParagraphAttributes(style, false);
			}

			// Make the entire paragraph right aligned
			if (event.getPropertyName().equals(StyleChangeNotify.RIGHT_ALIGN)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setAlignment(style, StyleConstants.ALIGN_RIGHT);
				setParagraphAttributes(style, false);
			}

			// Make the entire paragraph justify aligned
			if (event.getPropertyName().equals(StyleChangeNotify.JUSTIFY_ALIGN)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setAlignment(style,
						StyleConstants.ALIGN_JUSTIFIED);
				setParagraphAttributes(style, false);
			}

			// Make the entire paragraph single spaced
			if (event.getPropertyName().equals(StyleChangeNotify.SINGLESPACE)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setLineSpacing(style,
						LineSpacingButtonGroup.SingleSpace.SINGLE_SPACING);
				setParagraphAttributes(style, false);
			}

			// Make the entire paragraph one half spaced
			if (event.getPropertyName().equals(StyleChangeNotify.ONEHALFSPACE)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setLineSpacing(style,
						LineSpacingButtonGroup.OneHalfSpace.ONEHALF_SPACING);
				setParagraphAttributes(style, false);
			}

			// Make the entire paragraph double spaced
			if (event.getPropertyName().equals(StyleChangeNotify.DOUBLESPACE)) {
				Style mstyle = getLogicalStyle();
				if (mstyle == null) {
					mstyle = mainStyle;
				}
				Style style = defaultStyleContext.addStyle(null, mstyle);
				StyleConstants.setLineSpacing(style,
						LineSpacingButtonGroup.DoubleSpace.DOUBLE_SPACING);
				setParagraphAttributes(style, false);
			}

			/*
			 * The user wants to increase the left indentation of the entire
			 * selection
			 */
			if (event.getPropertyName().equals(StyleChangeNotify.LEFT_INDENT)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null)
					style = defaultStyleContext.addStyle(null, currentStyle);
				else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				Float newValue = new Float(Float.parseFloat((String) event
						.getNewValue()));
				Float currentValue = (Float) this.getParagraphAttributes()
						.getAttribute(StyleConstants.LeftIndent);

				if (currentValue == null) {
					style.addAttribute(StyleConstants.LeftIndent,
							(Object) newValue);
					setParagraphAttributes(style, false);
				} else if (currentValue < newValue) {
					/*
					 * The user specified left margin value is greater than the
					 * current left margin value of the paragraph
					 */
					if (currentValue < TextPane.MAXIMUM_LEFT_INDENT) {
						style.removeAttribute(StyleConstants.LeftIndent);
						style.addAttribute(StyleConstants.LeftIndent,
								(Object) newValue);
						setParagraphAttributes(style, false);
					}
				} else if (currentValue > newValue) {
					/*
					 * This can happen when the user clicks the right indent
					 * button with the intention of decreasing the right
					 * indentation of the current paragraph
					 */

					style.addAttribute(StyleConstants.LeftIndent,
							(Object) newValue);
					setParagraphAttributes(style, false);
				}

				setCaretPosition(dot);
			}

			/* Change the foreground color for the entire selection */
			if (event.getPropertyName().equals(
					StyleChangeNotify.FOREGROUND_COLOR)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				Color oldValue = (Color) getParagraphAttributes().getAttribute(
						StyleConstants.Foreground);
				Color newValue = (Color) event.getNewValue();

				if (newValue == null) {
					return;
				}

				if (oldValue != newValue) {
					StyleConstants.setForeground(style, newValue);
					if (dot >= mark) {
						document.setCharacterAttributes(mark, (dot - mark),
								style, false);
					} else {
						document.setCharacterAttributes(dot, (mark - dot),
								style, false);
					}
				}
				setCaretPosition(dot);
			}

			/* Change the background color for the entire selection */
			if (event.getPropertyName().equals(
					StyleChangeNotify.BACKGROUND_COLOR)) {
				Style style;
				Style currentStyle = getLogicalStyle();
				if (currentStyle != null) {
					style = defaultStyleContext.addStyle(null, currentStyle);
				} else {
					style = defaultStyleContext.addStyle(null, mainStyle);
				}

				Color oldValue = (Color) getParagraphAttributes().getAttribute(
						StyleConstants.Background);
				Color newValue = (Color) event.getNewValue();

				if (newValue == null) {
					return;
				}

				if (oldValue != newValue) {
					StyleConstants.setBackground(style, newValue);
					if (dot >= mark) {
						document.setCharacterAttributes(mark, (dot - mark),
								style, false);
					} else {
						document.setCharacterAttributes(dot, (mark - dot),
								style, false);
					}
				}
				setCaretPosition(dot);
			}
		}
	}

	@Override
	public void caretUpdate(CaretEvent event) {
		dot = event.getDot();
		mark = event.getMark();

		/*
		 * Get the logical style at the current caret position and send the
		 * underlying style to the style observer
		 */
		Element charElem = document.getCharacterElement(dot);
		currentStyleObserver.setCharacterStyle(charElem);

		/*
		 * In case there is a selection, the UI is updated using the values from
		 * the first paragraph
		 */
		Element paraElem = document.getParagraphElement(dot);
		currentStyleObserver.setParagraphStyle(paraElem);
	}

	/**
	 * @return Document of the Text Pane
	 */
	public static Document getTextPaneDocument() {
		return TextPane.document;
	}

	/**
	 * @return EditorKit of the Text Pane
	 */
	public static EditorKit getTextPaneEditorKit() {
		return TextPane.editorKit;
	}

	/**
	 * @return The current position of the caret dot
	 */
	public static int getDot() {
		return TextPane.dot;
	}

	/**
	 * @return The current position of the caret mark
	 */
	public static int getMark() {
		return TextPane.mark;
	}

	/**
	 * @return The main style of the text document
	 */
	public Style getMainStyle() {
		return this.mainStyle;
	}

	/**
	 * Create a header for the text pane
	 */
	public void createHeader() {
		if (this.header == null) {
			this.header = new Header();
			this.header.setSize(Header.WIDTH, Header.HEIGHT);
		}

		/* Add header to the text component as one of its children */
		Component[] comps = this.getComponents();
		if (comps == null) {
			return;
		}

		int n = comps.length;
		for (int i = 0; i < n; i++) {
			if (this.getComponent(i).equals(this.header)) {
				return; /* Header exists */
			}
		}

		this.add(this.header);
		TextPane.isHFsizevalid = false;

		/* Repaint the entire text pane starting with the root view */
		View v = this.getUI().getRootView(this);
		int m = v.getViewCount();
		for (int i = 0; i < m; i++) {

			/* Search for the multi-page view and re-layout it */
			View vv = v.getView(i);
			if (vv instanceof MultiPageSectionView) {
				((MultiPageSectionView) vv).layout(0, Integer.MAX_VALUE);
				break;
			}

			int l = vv.getViewCount();
			for (int k = 0; k < l; k++) {

				/* Search for the multi-page view and re-layout it */
				View view = vv.getView(k);
				if (view instanceof MultiPageSectionView) {
					((MultiPageSectionView) view).layout(0, Integer.MAX_VALUE);
					break;
				}
			}
		}

		this.repaint();
	}

	/**
	 * Remove header from the text pane
	 */
	public void removeHeader() {
		if (this.header == null) {
			return;
		}

		/* Remove header */
		Component[] comps = this.getComponents();
		if (comps == null) {
			return;
		}

		int n = comps.length;
		for (int i = 0; i < n; i++) {
			if (this.getComponent(i).equals(this.header)) {
				this.remove(this.header);
				this.header = null;
				TextPane.isHFsizevalid = false;
				return;
			}
		}
	}

	/**
	 * @return Header for the text pane
	 */
	public JEditorPane getHeader() {
		return this.header;
	}

	/**
	 * @return Whether the current sizes of the header and footer are valid.
	 *         Whenever header/footer is newly added to the text pane, this
	 *         field is reset to indicate to the section view responsible
	 *         rendering the header/footer needs to do its size calculation
	 *         again.
	 */
	public static boolean isHFsizevalid() {
		return TextPane.isHFsizevalid;
	}

	/**
	 * Sets the validity of the header and footer. This will be mainly be used
	 * by section view to indicate that it has changed its calculation to
	 * reflect the changing height of the newly added/removed size of the
	 * header/footer
	 * 
	 * @param HFsizevalid
	 *            - set to true when the size calculation for the newly added
	 *            header/footer is complete
	 */
	public static void setHFsizevalid(boolean HFsizevalid) {
		TextPane.isHFsizevalid = HFsizevalid;
	}

	@Override
	public int print(Graphics g, PageFormat pageFormat, int pageIndex)
			throws PrinterException {
		if (pageIndex > 0) { /* We have only one page, and 'page' is zero-based */
			return Printable.NO_SUCH_PAGE;
		}

		/*
		 * User (0,0) is typically outside the imageable area, so we must
		 * translate by the X and Y values in the PageFormat to avoid clipping
		 */
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());

		/* Now print the window and its visible contents */
		this.printAll(g2d);

		/* tell the caller that this page is part of the printed document */
		return Printable.PAGE_EXISTS;
	}
}